package com.fusetech.flow.audit.apiService.impl;


import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fusetech.flow.audit.constant.ErpAuditConstant;
import com.fusetech.flow.audit.domain.*;
import com.fusetech.flow.audit.apiService.IAuditApiService;
import com.fusetech.flow.audit.model.Audit;
import com.fusetech.flow.audit.model.AuditResponse;
import com.fusetech.flow.audit.model.CurrentNode;
import com.fusetech.flow.audit.service.*;
import com.fusetech.flow.audit.service.dto.AuditNodeDTO;
import com.fusetech.flow.audit.service.dto.CurrentAuditNodeDTO;
import com.fusetech.flow.audit.service.mapstruct.AuditNodeWrapper;
import com.fusetech.flow.audit.supportService.IAuditChcekService;
import com.fusetech.os.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * 审核Api
 * @author Liuwq
 * @version 2023-05-10
 */
@Service
@Transactional
@Primary
public class AuditApiService implements IAuditApiService {

    @Autowired
    private AuditRecordService auditRecordService;
    @Autowired
    private AuditProcessService auditProcessService;
    @Autowired
    private AuditProcessFormService auditProcessFormService;

    @Autowired
    private AuditNodeService auditNodeService;

    @Autowired
    private CurrentAuditNodeService currentAuditNodeService;

    @Autowired
    private AuditNodeWrapper auditNodeWrapperImpl;

    @Autowired
    private IAuditChcekService iAuditChcekService;

    /**
     * 异常信息
     */
    private String errMsg;

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    /**
     * 审核api
     *
     * @param audit 审计
     * @return {@link AuditResponse}
     */
    public AuditResponse auditApi(Audit audit) {
        this.errMsg = new String();
        AuditResponse response = new AuditResponse();
        response = iAuditChcekService.checkAuditParam(audit,response);//检验参数
        if(response.getCode().equals("200")){
            response = doAuditApi( audit, response);//进入具体方法
        }
        return response;
    }

    /**
     * 做审计api
     *
     * @param audit    审计
     * @param response 响应
     * @return {@link AuditResponse}
     */
    AuditResponse doAuditApi(Audit audit,AuditResponse response){
        response = checkAuditUserRoleAndExecute(audit, response);//检验权限并执行
        response = checkResponse(response);//检查是否有过程中异常信息，有则添加
        return response;
    }


    /**
     * 检查审核用户角色和执行
     *
     * @param audit    审计
     * @param response 响应
     * @return {@link AuditResponse}
     */
    AuditResponse checkAuditUserRoleAndExecute(Audit audit, AuditResponse response) {//
        String documentId = audit.getDocumentId();
        CurrentAuditNodeDTO currentAuditNode = currentAuditNodeService.getCurrentAuditNodeDTOByDocumentId(documentId);
        //还需要判断当前是否已有人在审核
        if (!ObjectUtils.isEmpty(currentAuditNode)) {//已有审核记录
            if (iAuditChcekService.checkNodeUserRoleB(audit.getListSysRole(), audit.getAuditor(), audit.getDocumentId())) {
                if(!currentAuditNode.getNodeId().equals(audit.getCurrentNodeId())){
                    response.setCode("500");
                    response.setMsg("传入当前步骤数与流程系统当前步骤数不符合!已有人操作此审批，请勿重复操作");
                }else {
                    response = doAudit(audit, response,currentAuditNode);
                }
            } else {
                response.setCode("500");
                response.setMsg("当前用户没有审批当前节点该类型表单权限");
            }
        } else {//发起初始审批
            if (iAuditChcekService.checkProcessUserRole(audit.getListSysRole(), audit.getDocumentType())) {
                response = doAuditStart(audit, response);
            } else {//没有权限
                response.setCode("500");
                response.setMsg("当前用户没有进入审批流程该类型表单权限");
            }
        }
        return response;
    }

    /**
     * 执行 审核
     *
     * @param audit            审计
     * @param auditResponse    审计响应
     * @param currentAuditNode 当前审计节点
     * @return {@link AuditResponse }
     * @author Liuwq
     * @date 2023/06/14
     *///修改传入审核对象的状态
    AuditResponse doAudit(Audit audit, AuditResponse auditResponse,CurrentAuditNodeDTO currentAuditNode) {//

        String status = audit.getStatus();

        if (ErpAuditConstant.AUDIT_STATUS_APPROVED.equals(status)) {//传入审核为通过时
            String auditProcessId = currentAuditNode.getAuditProcess().getId();//主流程配置表id
            String nextNodeIdBzs = currentAuditNode.getNextNodeNum();//下一节点步骤数

            AuditNodeDTO nodeRecord =currentAuditNode.getAuditNode();//记录节点写入流程记录表
            if (StringUtils.isNotEmpty(nextNodeIdBzs)) {//判断是否到最后节点
                AuditNodeDTO currentNode = currentAuditNode.getNextNode();//流程推动进入下个节点
                currentNode.setAuditProcessId(auditProcessId);//主流程配置表id
                currentNode.setNodeId(currentAuditNode.getNextNodeNum());//设置下一节点步骤数
                AuditNodeDTO nextNode = auditNodeService.getNextNodeDTOByProcessId(auditProcessId,currentNode);//去查询获取新的下一节点

                deleteCurrentAuditNodeById(currentAuditNode.getId());//此表是当前状态表要求 一个表单只有一个当前流程状态，唯一
                toEntityAndWrite(currentNode,audit,nextNode);//写入流程当前节点信息（流程）,及下节点信息
                toEntityAndWriteRecord(nodeRecord,audit);//写入流程审批记录

                auditResponse.setStatus(ErpAuditConstant.AUDIT_STATUS_APPROVED);
                if(!ObjectUtils.isEmpty(nextNode)){
                    auditResponse.setNodeName(nextNode.getNodeName());
                    auditResponse.setNodeId(nextNode.getNodeId());
                }
                auditResponse.setCode("200");
                auditResponse.setMsg("审批通过，进入下一节点");
            } else {//已到最后节点不在写入当前表
                deleteCurrentAuditNodeById(currentAuditNode.getId());//此表是当前状态表要求 一个表单只有一个当前流程状态，唯一
                toEntityAndWriteRecord(nodeRecord,audit);//写入流程审批记录

                auditResponse.setStatus(ErpAuditConstant.AUDIT_STATUS_FINISH);//审核已完成
                auditResponse.setCode("200");
                auditResponse.setMsg("审批已完成");
            }

        }
        if (ErpAuditConstant.AUDIT_STATUS_REJECTED.equals(status)) {//驳回上一节点
            String processId =currentAuditNode.getAuditProcess().getId();
            String currentNodeIdBzs =  currentAuditNode.getNodeId();
            AuditNode previousNode = auditNodeService.getPreviousNodeByProcessId(processId,currentNodeIdBzs);

            if (!ObjectUtils.isEmpty(previousNode)) {
                AuditNode nextNode = auditNodeService.getNextNodeByProcessId(processId,previousNode);//流程退回所以下一个节点是上一节点计算

                deleteCurrentAuditNodeById(currentAuditNode.getId());//此表是当前状态表要求 一个表单只有一个当前流程状态，唯一

                doWriteCurrentAuditNode(previousNode,audit,nextNode);//写入流程当前节点信息（流程）,及下节点信息

                AuditNodeDTO currentNode = currentAuditNode.getAuditNode();
                toEntityAndWriteRecord(currentNode,audit);//写入流程审批记录

                auditResponse.setStatus(ErpAuditConstant.AUDIT_STATUS_REJECTED);
                auditResponse.setNodeName(previousNode.getNodeName());
                auditResponse.setNodeId(previousNode.getNodeId());
                auditResponse.setCode("200");
                auditResponse.setMsg("审批驳回上一节点");
            } else {//已是第一节点，
                deleteCurrentAuditNodeById(currentAuditNode.getId());//此表是当前状态表要求 一个表单只有一个当前流程状态，唯一
                AuditNodeDTO currentNode = currentAuditNode.getAuditNode();
                toEntityAndWriteRecord(currentNode,audit);//写入流程审批记录

                auditResponse.setStatus(ErpAuditConstant.DOC_STATUS_DRAFT);//已经没有节点可退，直接退回草稿阶段
                auditResponse.setCode("200");
                auditResponse.setMsg("被驳回到退回草稿阶段");
            }
        }
        if (ErpAuditConstant.AUDIT_STATUS_REFUSE.equals(status)) {//审批不通过退回重新修改<br>* 该状态的文档为进行审批流程后，审批不通过退回重新修改
            deleteCurrentAuditNodeById(currentAuditNode.getId());//此表是当前状态表要求 一个表单只有一个当前流程状态，唯一
            AuditNodeDTO currentNode = currentAuditNode.getAuditNode();
            toEntityAndWriteRecord(currentNode,audit);//写入流程审批记录

            auditResponse.setStatus(ErpAuditConstant.DOC_STATUS_DRAFT);
            auditResponse.setCode("200");
            auditResponse.setMsg("审批不通过退回重新修改");
        }


        return auditResponse;
    }

    /**
     * doAuditStart 开始
     *
     * @param audit         审计
     * @param auditResponse 审计响应
     * @return {@link AuditResponse}
     */
    AuditResponse doAuditStart(Audit audit,AuditResponse auditResponse) {
        //根据表单文档类型查询属于那个流程，设置流程id
        String documentType = audit.getDocumentType();
        AuditProcess auditProcess = getAuditProcessByDocumentType(documentType);

        String processId = auditProcess.getId();
        AuditNode firstNode = auditNodeService.getFirstNodeByProcessId(processId);//流程开始所以当前一个节点是第一个节点
        if(ObjectUtil.isNotEmpty(firstNode)){
            //写入流程当前节点信息（流程开始）//流程开始所以new
            AuditNode nextNode = auditNodeService.getNextNodeByProcessId(processId,firstNode);
            doWriteCurrentAuditNode(firstNode,audit,nextNode);
            //开始节点还没审批所以不写入审批记录
            audit.setStatus(ErpAuditConstant.AUDIT_STATUS_START);
            doWriteAuditRecord(firstNode,audit);//写入流程审批记录
            //设置当前节点信息返回信息
            auditResponse.setStatus(ErpAuditConstant.AUDIT_STATUS_PENDING);
            auditResponse.setNodeName(firstNode.getNodeName());
            auditResponse.setNodeId(firstNode.getNodeId());
            auditResponse.setCode("200");
            auditResponse.setMsg("流程已开始");
        }else {
            auditResponse.setCode("500");
            auditResponse.setMsg("该流程没有配置节点");
        }
        return auditResponse;
    }
    void toEntityAndWrite(AuditNodeDTO currentNodeDto,Audit audit,AuditNodeDTO nextNodeDto){
        AuditNode currentNode = auditNodeWrapperImpl.toEntity(currentNodeDto);
        currentNode.setAuditProcessId(currentNodeDto.getAuditProcessId());//转换时会丢失这个
        AuditNode nextNode = auditNodeWrapperImpl.toEntity(nextNodeDto);
        doWriteCurrentAuditNode(currentNode,audit,nextNode);
    }


    /**
     * 写当前审计节点
     *
     * @param auditNode 审核节点
     * @param audit     审计
     * @param nextNode  下一个节点
     */
    void doWriteCurrentAuditNode(AuditNode auditNode,Audit audit,AuditNode nextNode){
        CurrentAuditNode currentAuditNode = new CurrentAuditNode();//new
        currentAuditNode.setAuditProcessId(auditNode.getAuditProcessId());
        currentAuditNode.setAuditNodeId(auditNode.getId());
        currentAuditNode.setNodeId(auditNode.getNodeId());//步骤数
        currentAuditNode.setDocumentId(audit.getDocumentId());
        currentAuditNode.setDocument(audit.getDocumentName());
        currentAuditNode.setDocumentType(audit.getDocumentType());
        if(!ObjectUtils.isEmpty(nextNode)){//如果nextNode不为空
            currentAuditNode.setNextNodeId(nextNode.getId());
            currentAuditNode.setNextNodeNum(nextNode.getNodeId());
        }
        currentAuditNodeService.save(currentAuditNode);
    }

    /**
     * 写入流程审批记录
     * @param currentNodeDto
     * @param audit
     */
    void toEntityAndWriteRecord(AuditNodeDTO currentNodeDto,Audit audit){
        AuditNode currentNode = auditNodeWrapperImpl.toEntity(currentNodeDto);
        currentNode.setAuditProcessId(currentNodeDto.getAuditProcessId());//转换时会丢失这个
        doWriteAuditRecord(currentNode,audit);
    }

    /**
     * 写入流程审批记录
     * @param auditNode
     * @param audit
     *
     */
    void doWriteAuditRecord(AuditNode auditNode,Audit audit){//写入流程审批记录
        AuditRecord auditRecord = new AuditRecord();//new
        auditRecord.setAuditProcessId(auditNode.getAuditProcessId());
        auditRecord.setAuditNodeId(auditNode.getId());
        auditRecord.setNodeId(auditNode.getNodeId());//步骤数
        auditRecord.setDocumentId(audit.getDocumentId());
        auditRecord.setDocument(audit.getDocumentName());
        auditRecord.setDocumentType(audit.getDocumentType());
        auditRecord.setStatus(audit.getStatus());
        auditRecord.setAuditorId(audit.getAuditor().getUserId().toString());//[框架对应转换]
        auditRecord.setComments(audit.getComments());//审批意见
        auditRecordService.save(auditRecord);
    }


    /**
     * 获得当前节点
     *
     * @param documentId 文档id
     * @return {@link CurrentNode}
     */
    public CurrentNode getCurrentNode(String documentId) {
        CurrentAuditNodeDTO currentAuditNodeDTO = currentAuditNodeService.getCurrentAuditNodeDTOByDocumentId(documentId);
        AuditNodeDTO an = currentAuditNodeDTO.getAuditNode();
        CurrentNode cn = new CurrentNode();
        cn.setCurrentNodeBZS(an.getNodeId());
        cn.setCurrentNodeName(an.getNodeName());
        return cn;
    }


    /**
     * 文档类型get AuditProcess
     *
     * @param documentType 文档类型
     * @return {@link AuditProcess}
     */
    AuditProcess getAuditProcessByDocumentType(String documentType)  {
        AuditProcess auditProcess =new AuditProcess();
        QueryWrapper queryForm = new QueryWrapper();
        queryForm.eq("form_id", documentType);
        List<AuditProcessForm> form = auditProcessFormService.list(queryForm);//避免数据库数据不正常时报错，取list
        if(CollectionUtil.isNotEmpty(form)){
             auditProcess = auditProcessService.getById(form.get(0).getAuditProcessId());
        }else {
            //throw new ServiceException("该表单场景没有配置对应流程，请在配置中添加");
            //System.out.println("该表单场景没有配置对应流程，请在配置中添加");
            setErrMsg("该表单场景没有配置对应流程，请在配置中添加");
        }
        return auditProcess;
    }



    /**
     *
     * 此表是当前状态表要求 一个表单只有一个当前流程状态，唯一
     * 所以先删除已审批节点信息，然后在进行写入
     * @param currentAuditNodeId
     */
    void deleteCurrentAuditNodeById(String currentAuditNodeId){
        currentAuditNodeService.removeById(currentAuditNodeId);
    }

    /**
     * 检查反应
     *
     * @param response 响应
     * @return {@link AuditResponse}
     */
    AuditResponse checkResponse(AuditResponse response){
        if(StringUtils.isNotEmpty(getErrMsg())){
            response.setCode("500");
            response.setMsg(getErrMsg());
        }
        return response;
    }


}
